// i.r(t), i.d(t, { default: () => SweepPathModule })

import * as THREE from "three"
import Module from "./Module"
import SweepData from "../data/SweepData"

class Heap {
  comparator: any
  nodes: any[]
  constructor(e) {
    ;(this.comparator = e), (this.nodes = [])
  }
  push(e) {
    return this.nodes.push(e), this.siftdown(0, this.nodes.length - 1)
  }
  pop() {
    let e, t
    return this.nodes.length && ((e = this.nodes.pop()), e)
      ? (this.nodes.length ? ((t = this.nodes[0]), (this.nodes[0] = e), this.siftup(0)) : (t = e), t)
      : null
  }
  peek() {
    return this.nodes[0]
  }
  contains(e) {
    return -1 !== this.nodes.indexOf(e)
  }
  replace(e) {
    if (this.nodes.length) {
      const t = this.nodes[0]
      return (this.nodes[0] = e), this.siftup(0), t
    }
    return null
  }
  _pushpop(e, t, i) {
    let n
    const s = i || this.defaultCompare
    return e.length && s(e[0], t) < 0 && ((n = [e[0], t]), (t = n[0]), (e[0] = n[1]), this._siftup(e, 0, s)), t
  }
  pushpop(e) {
    return this._pushpop(this.nodes, e, this.comparator)
  }
  _heapify(e, t) {
    let i, n, s, r, o, a
    const l = Math.floor(e.length / 2),
      c = function () {
        for (a = [], s = 0, o = l; 0 <= o ? s < o : s > o; 0 <= o ? s++ : s--) a.push(s)
        return a
      }
        .apply(this)
        .reverse(),
      h = []
    for (n = 0, r = c.length; n < r; n++) (i = c[n]), h.push(this._siftup(e, i, t))
    return h
  }
  heapify() {
    let e, t, i, n, s, r
    const o = Math.floor(this.nodes.length / 2),
      a = function () {
        for (r = [], i = 0, s = o; 0 <= s ? i < s : i > s; 0 <= s ? i++ : i--) r.push(i)
        return r
      }
        .apply(this)
        .reverse(),
      l = []
    for (t = 0, n = a.length; t < n; t++) (e = a[t]), l.push(this.siftup(e))
    return l
  }
  updateItem(e) {
    const t = this.nodes.indexOf(e)
    return -1 === t ? null : (this.siftdown(0, t), this.siftup(t))
  }
  clear() {
    return (this.nodes = [])
  }
  empty() {
    return 0 === this.nodes.length
  }
  size() {
    return this.nodes.length
  }
  clone() {
    const e = new Heap(this.comparator)
    return (e.nodes = this.nodes.slice(0)), e
  }
  toArray() {
    return this.nodes.slice(0)
  }
  insert(e) {
    return this.push(e)
  }
  top() {
    return this.peek()
  }
  front() {
    return this.peek()
  }
  has(e) {
    return this.contains(e)
  }
  copy() {
    return this.clone()
  }
  nlargest(e) {
    let t, i, n
    const s = this.nodes.slice(0, e)
    if (!s.length) return s
    this._heapify(s, this.comparator)
    const r = this.nodes.slice(e)
    for (i = 0, n = r.length; i < n; i++) (t = r[i]), this._pushpop(s, t, this.comparator)
    return s.sort(this.comparator).reverse()
  }
  defaultCompare(e, t) {
    return e < t ? -1 : e > t ? 1 : 0
  }
  _siftdown(e, t, i, n) {
    let s, r
    const o = n || this.defaultCompare,
      a = e[i]
    for (; i > t && ((r = (i - 1) >> 1), (s = e[r]), o(a, s) < 0); ) (e[i] = s), (i = r)
    return (e[i] = a)
  }
  siftdown(e, t) {
    return this._siftdown(this.nodes, e, t, this.comparator)
  }
  _siftup(e, t, i) {
    let n, s
    const r = i || this.defaultCompare,
      o = e.length,
      a = t,
      l = e[t]
    for (n = 2 * t + 1; n < o; ) (s = n + 1), s < o && !(r(e[n], e[s]) < 0) && (n = s), (e[t] = e[n]), (n = 2 * (t = n) + 1)
    return (e[t] = l), this._siftdown(e, a, t, r)
  }
  siftup(e) {
    return this._siftup(this.nodes, e, this.comparator)
  }
}
function a(e, t) {
  for (const i of t) if (i === e) return !0
  return !1
}
var n = {
  0: "Success",
  1: "NoPath",
  2: "Timeout",
  Success: 0,
  NoPath: 1,
  Timeout: 2
}

function l(e) {
  if (void 0 !== e.parent) {
    const t = l(e.parent)
    return t.push(e.data), t
  }
  return [e.data]
}
function c(e) {
  return e.toString()
}
function h(e, t) {
  if (e.f && t.f) return e.f - t.f
  throw new Error('heapComparator() -> Property "f" is undefined.')
}
const d = Object.freeze({ sweeps: { maxNeighborDistance: 50 } })
import { RestrictedSweepsClearedMessge, RestrictedSweepsSetMessge } from "../message/sweeps.messge"
import EngineContext from "../EngineContext"

export default class SweepPathModule extends Module {
  engine: EngineContext
  sweepData: SweepData
  validNeighbors: any
  maxNeighborDistance: any
  restrictedSweeps: any[]
  distanceMap: any
  constructor() {
    super(...arguments)
    this.name = "sweep-path"
  }
  async init(e, t) {
    this.engine = t
    this.sweepData = await t.market.waitForData(SweepData)
    this.validNeighbors = {}
    this.maxNeighborDistance = e.maxNeighborDistance || d.sweeps.maxNeighborDistance
    this.buildDistanceMap()
  }
  setRestrictedSweeps(e, t = 0) {
    if (((this.restrictedSweeps = []), e)) {
      for (let i = t; i < e.length; i++) this.restrictedSweeps.push(e[i].id)
      this.engine.broadcast(new RestrictedSweepsSetMessge(this.restrictedSweeps))
    } else (this.restrictedSweeps = void 0), this.engine.broadcast(new RestrictedSweepsClearedMessge())
  }
  findShortestPath(e, t, i, s, r, o?, d = 5000) {
    const u = this.sweepData.getSweep(e)
    const p = this.sweepData.getSweep(t)
    const m = (e => {
      void 0 === e.timeout && (e.timeout = 1 / 0)
      const t = e.hash || c,
        i: any = { data: e.start, g: 0, h: e.heuristic(e.start) }
      let s = i
      i.f = i.h
      const r = []
      const o = new Heap(h)
      const d = {}
      o.push(i), (d[t(i.data)] = i)
      const u = Date.now()
      for (; o.size(); ) {
        if (Date.now() - u > e.timeout) return { status: n.Timeout, cost: s.g, path: l(s) }
        const i = o.pop()
        if ((delete d[t(i.data)], i && e.isEnd(i.data))) return { status: n.Success, cost: i.g, path: l(i) }
        a((p = t(i.data)), (m = r)) || m.push(p)
        const c = e.neighbor(i.data)
        for (const n of c) {
          if (a(t(n), r)) continue
          const l = i.g + e.distance(i.data, n)
          let c = d[t(n)],
            h = !1
          if (void 0 === c) (c = { data: n, g: 0, h: 0, f: 0 }), (d[t(n)] = c)
          else {
            if (c.g < l) continue
            h = !0
          }
          c.parent = i || void 0
          c.g = l
          c.h = e.heuristic(n)
          c.f = l + c.h
          c.h < s.h && (s = c)
          h ? o.heapify() : o.push(c)
        }
      }
      var p, m
      return { status: n.NoPath, cost: s.g, path: l(s) }
    })({
      start: u,
      isEnd: e => e === p,
      neighbor: e => this.getValidNeighbors(e.id, o),
      distance: (e, t) => this.getDistance(e.id, t.id),
      heuristic: (e, t?) => 1,
      hash: e => e.id,
      timeout: d
    })
    return m.status === n.Success && m.path.length < r ? (this.addSweepsNearPath(m.path, i), this.filterCloseSweepsFromPath(m.path, s)) : null
  }
  addSweepsNearPath(e, t) {
    const i = new THREE.Vector3(),
      n = new THREE.Vector3(),
      r = new THREE.Vector3(),
      o = new THREE.Vector3(),
      a = new THREE.Vector3(),
      l = new THREE.Vector3(),
      c = [],
      h = new THREE.Vector3(),
      d = (e, t, i) => (r.copy(t).sub(e), r.dot(i)),
      u = (e, t) => d(h, e.position, i) - d(h, t.position, i)
    let p = 0
    for (; p < e.length - 1; ) {
      const s = e[p].id,
        d = e[p + 1].id,
        m = this.sweepData.getSweep(s),
        g = this.sweepData.getSweep(d)
      h.copy(m.position), (c.length = 0), i.copy(g.position).sub(h).normalize()
      const f = this.findConnectedSweeps(m, this.maxNeighborDistance),
        v = this.findConnectedSweeps(g, this.maxNeighborDistance),
        y = f.concat(v)
      for (const e of y) {
        const s = r.copy(e.position).sub(h).dot(i)
        if (s > 0) {
          a.copy(i), a.multiplyScalar(s), o.copy(r), o.sub(a)
          if (o.length() < t) {
            n.copy(i).negate(), l.copy(e.position).sub(g.position)
            l.dot(n) > 0 && c.push(e)
          }
        }
      }
      if (c.length > 0) {
        c.sort(u)
        for (let t = e.length + c.length - 1; t >= p + c.length; t--) e[t] = e[t - c.length]
        for (let t = 0; t < c.length; t++) e[t + p + 1] = c[t]
      }
      p += c.length + 1
    }
  }
  findConnectedSweeps(e, t, i = 2) {
    const n = []
    return this._findConnectedSweeps(e, e, t, n, {}, i, 0), n
  }
  _findConnectedSweeps(e, t, i, n, s, r, o) {
    const a = this.getValidNeighbors(e.id)
    for (const e of a)
      if (!s[e.id]) {
        e.position.distanceTo(t.position) < i && (n.push(e), (s[e.id] = !0), o < r && this._findConnectedSweeps(e, t, i, n, s, r, o + 1))
      }
  }
  getValidNeighbors(e, t?) {
    let i = this.validNeighbors[e]
    if (!i) {
      ;(i = []), (this.validNeighbors[e] = i)
      const n = this.sweepData.getSweep(e),
        s = n.neighbours
      for (const r of s) {
        const s = this.sweepData.getSweep(r),
          o = this.getDistance(e, r)
        if (!s.enabled) continue
        if (o > this.maxNeighborDistance) continue
        let a = !0
        if (t) {
          const e = s.position.clone().sub(n.position).normalize()
          a = 0 === t(n.position, e, o).length
        }
        a && i.push(s)
      }
    }
    return i
  }
  filterCloseSweepsFromPath(e, t) {
    const i = []
    let n = null
    let s = !1
    for (const r of e) (s = (n && r.position.distanceTo(n.position) < t) || !1), (n && s) || (i.push(r), (n = r))
    return i.length < 2 ? e : (s && e.length > 1 && (i[i.length - 1] = e[e.length - 1]), i)
  }
  getCurveForPath(e) {
    let t = 0
    const i = [0]
    for (let n = 1; n < e.length; n++) (t += e[n - 1].distanceTo(e[n])), i.push(t)
    const n = [0]
    for (let s = 1; s < e.length; s++) n.push(i[s] / t)
    const r = new THREE.CatmullRomCurve3(e, !1)
    return {
      curve: new THREE.CatmullRomCurve3(r.getSpacedPoints(2 * t).concat(e[e.length - 1])),
      totalLength: t,
      sourceDistances: i,
      normalSourceDistances: n
    }
  }
  buildDistanceMap() {
    const e = {},
      t = new THREE.Vector3(0, 0, 0)
    this.sweepData.iterate(i => {
      const n = {},
        s = i.neighbours
      for (const e of s) {
        const s = this.sweepData.getSweep(e)
        t.copy(i.position).sub(s.position)
        let r,
          o = Math.max(0, Math.abs(t.y) - 0.2),
          a = Math.sqrt(t.x * t.x + t.z * t.z)
        o > 0 ? ((o = Math.pow(4 * o, 2)), (a = Math.pow(a, 2)), (r = Math.sqrt(o * o + a * a))) : (r = t.length()), (n[s.id] = r)
      }
      e[i.id] = n
    }),
      (this.distanceMap = e)
  }
  getDistance(e, t) {
    return this.distanceMap[e][t]
  }
}
